/*
 * DevPlugin.LogModule
 * Andy Tidball
 * 
 * Project: Black Lightning Developer Plugin
 * Copyright: GNU General Public License
 */

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using BlackLightning.PluginLib;
using BlackLightning.PluginLib.Modules;
using BlackLightning.PluginLib.Modules.Packets;

[assembly: BLModule(typeof(DevPlugin.LogModule))]

namespace DevPlugin {
	/// <summary>
	/// A module that allows the user to incoming and outgoing traffic to files.
	/// </summary>
	[ModuleName("Log")]
	public class LogModule : Module {
        /// <summary>
        /// Records the state of a single open log file.
        /// </summary>
        private class LogState {
            // member data
            private string _Filename;
            private bool   _Incoming;
            private bool   _Outgoing;

            /// <summary>
            /// Creates a new LogState.
            /// </summary>
            /// <param name="Filename">The filename to log to.</param>
            /// <param name="Incoming">Whether or not incoming data should be logged.</param>
            /// <param name="Outgoing">Whether or not outgoing data should be logged.</param>
            public LogState(string Filename, bool Incoming, bool Outgoing) {
                _Filename = Filename;
                _Incoming = Incoming;
                _Outgoing = Outgoing;
            }

            /// <summary>
            /// Gets the filename of the log file.
            /// </summary>
            public string Filename {
                get {
                    return _Filename;
                }
            }

            /// <summary>
            /// Gets whether or not the log file is recording incoming data.
            /// </summary>
            public bool Incoming {
                get {
                    return _Incoming;
                }
            }

            /// <summary>
            /// Gets whether or not the log file is recording outgoing data.
            /// </summary>
            public bool Outgoing {
                get {
                    return _Outgoing;
                }
            }
        }

        /// <summary>
        /// The list of active logs.
        /// </summary>
        private List<LogState> _Logs;

        /// <summary>
        /// The root path where logs will be stored.
        /// </summary>
        private string _RootPath;

		/// <summary>
		/// Gets a property of the module.
		/// </summary>
		/// <param name="Property">The property of the module that Black Lightning is requesting.</param>
		/// <returns>The value of the requested property, or null if this module doesn't use that property.</returns>
		public override object GetProperty(ModuleProperty Property) {
			switch (Property) {
				case ModuleProperty.HelpText: return global::DevPlugin.Properties.Resources.LogModule_HelpText;
				default:                      return null;
			}
		}

		/// <summary>
		/// Called by Black Lightning whenever it has a private message for us.
		/// </summary>
		/// <param name="Message">The private message that Black Lightning is sending to us.</param>
		public override void HandleMessage(Message Message) {
			if (Message is LoadedMessage) {
                // create our member data
                _Logs     = new List<LogState>();
                _RootPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);

                // register to receive all packets not bound for BL
                this.RegisterPacket(new BLPacketFilter(Program.FrontEnd, Program.GameServer));
                this.RegisterPacket(new BLPacketFilter(Program.GameServer, Program.FrontEnd));

                // register to receive "@dev log" commands
                this.RegisterPacket(new CommandPacketFilter(Program.FrontEnd, Program.BlackLightning, "dev", new string[]{"log"}));

                // register our root path as an option
                this.RegisterOption(global::DevPlugin.Properties.Resources.LogModule_RootPath_Name, typeof(string), Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), global::DevPlugin.Properties.Resources.LogModule_RootPath_Description, null, false, false, null, typeof(System.Windows.Forms.Design.FolderNameEditor));
            } else if (Message is OptionValueChangedMessage) {
                OptionValueChangedMessage M = Message as OptionValueChangedMessage;
                if (M.Name == global::DevPlugin.Properties.Resources.LogModule_RootPath_Name) {
                    _RootPath = M.NewValue as string;
                }
            }
		}

		/// <summary>
		/// Called by Black Lightning whenever it has a packet for us to process.
		/// </summary>
		/// <param name="Packet">The packet that we should handle.</param>
		public override void HandlePacket(ref BLPacket Packet) {
            // check what this is
            if (Packet is CommandPacket && Packet.Destination == Program.BlackLightning) {
                // dev command
                HandlePacket_DevCommand(Packet as CommandPacket);
                Packet.Suppress();
            } else if (Packet is BLPacket) {
                // data to be potentially logged
                HandlePacket_LogData(Packet as BLPacket);
            }
        }

        /// <summary>
        /// Handles an "@dev log" command.
        /// </summary>
        /// <param name="Packet">The packet containing the command.</param>
        private void HandlePacket_DevCommand(CommandPacket Packet) {
            // make sure we have enough arguments
            if (Packet.Arguments.Length < 2) {
                this.SendToFrontEnd(global::DevPlugin.Properties.Resources.LogModule_LogUsage);
                return;
            }

            // check the second argument
            bool Incoming = false;
            bool Outgoing = false;
            bool Close = false;
            bool List = false;
            switch (Packet.Arguments[1].ToLower()) {
                case "in":
                    Incoming = true;
                    break;
                case "out":
                    Outgoing = true;
                    break;
                case "both":
                    Incoming = true;
                    Outgoing = true;
                    break;
                case "close":
                    Close = true;
                    break;
                case "list":
                    List = true;
                    break;
                case "default":
                    this.SendToFrontEnd(global::DevPlugin.Properties.Resources.LogModule_LogUsage);
                    return;
            }

            // if we just need to list the logs, do that
            if (List) {
                ListLogs();
                return;
            }

            // make sure we have a filename argument
            if (Packet.Arguments.Length < 3) {
                this.SendToFrontEnd(global::DevPlugin.Properties.Resources.LogModule_LogUsage);
                return;
            }

            // get the filename
            string Filename = Packet.Arguments[2];

            // check what we're doing
            if (Close) {
                CloseLog(Filename);
            } else {
                OpenLog(Filename, Incoming, Outgoing);
            }
        }

        /// <summary>
        /// Handles packets that might need to be logged.
        /// </summary>
        /// <param name="Packet">The packet to handle.</param>
        private void HandlePacket_LogData(BLPacket Packet) {
            // run through each of our logs and write the data to the appropriate ones
            foreach (LogState Log in _Logs) {
                if (Packet.Destination == Program.FrontEnd && Log.Incoming) {
                    LogData(Log.Filename, true, Packet.RawInput);
                } else if (Packet.Destination == Program.GameServer && Log.Outgoing) {
                    LogData(Log.Filename, false, Packet.RawInput);
                }
            }
        }

        /// <summary>
        /// Sends a list of active logs to the front end.
        /// </summary>
        private void ListLogs() {
            StringBuilder Output = new StringBuilder();
            Output.AppendLine("Currently open logs:");
            foreach (LogState Log in _Logs) {
                Output.Append("  ");
                Output.Append(Log.Filename);
                Output.Append(" (");
                if (Log.Incoming && Log.Outgoing) {
                    Output.Append("incoming, outgoing");
                } else if (Log.Incoming) {
                    Output.Append("incoming");
                } else if (Log.Outgoing) {
                    Output.Append("outgoing");
                }
                Output.Append(")");
                Output.AppendLine();
            }
            this.SendToFrontEnd(Output.ToString());
        }

        /// <summary>
        /// Opens a new log.
        /// </summary>
        /// <param name="Filename">The filename to open and log to.</param>
        /// <param name="Incoming">Whether or not incoming data should be logged.</param>
        /// <param name="Outgoing">Whether or not outgoing data should be logged.</param>
        private void OpenLog(string Filename, bool Incoming, bool Outgoing) {
            // make sure we don't already have an active log with this filename
            foreach (LogState Log in _Logs) {
                if (Log.Filename == Filename) {
                    this.SendToFrontEnd(string.Format("A log with filename '{0}' is already active.", Filename));
                    return;
                }
            }

            // create a new log state and add it to the list
            _Logs.Add(new LogState(Filename, Incoming, Outgoing));
            this.SendToFrontEnd(string.Format("Now logging to '{0}'.", Filename));
        }

        /// <summary>
        /// Closes an open log.
        /// </summary>
        /// <param name="Filename">The filename of the log to close.</param>
        private void CloseLog(string Filename) {
            for (int i = 0; i < _Logs.Count; ++i) {
                if (_Logs[i].Filename == Filename) {
                    _Logs.RemoveAt(i);
                    this.SendToFrontEnd(string.Format("Log '{0}' has been successfully closed.", Filename));
                    return;
                }
            }
            this.SendToFrontEnd(string.Format("Log '{0}' isn't currently open.", Filename));
        }

        /// <summary>
        /// Logs some data to a log.
        /// </summary>
        /// <param name="Filename">The filename to log to.</param>
        /// <param name="Incoming">Whether or not the data is incoming (otherwise it's outgoing).</param>
        /// <param name="Data">The data string to log.</param>
        private void LogData(string Filename, bool Incoming, string Data) {
            // transform the data appropriately
            Data = Data.Replace(Environment.NewLine, "(\\n)");
            Data = (Incoming ? "incoming" : "outgoing") + ": " + Data;

            // build the full filename
            Filename = _RootPath + "\\" + Filename + ".txt";

            // append the data to the file
            StreamWriter Writer = new StreamWriter(Filename, true);
            Writer.WriteLine(Data);
            Writer.Close();
        }
	}
}
